#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "parsedate.h"

#ifndef IS_ALPHA
#define IS_ALPHA(x)          (((x >= 'A') && (x <= 'Z')) || ((x >= 'a') && (x <= 'z')))
#endif

#ifndef IS_DIGIT
#define IS_DIGIT(x)          ((x >= '0') && (x <= '9'))
#endif

#ifndef IS_WHITE_SPACE
#define IS_WHITE_SPACE(x)    ((x == ' ') || (x == '\t'))
#endif

typedef enum {RFC822_DATE_FORMAT, RFC850_DATE_FORMAT, ANSI_C_DATE_FORMAT} eDateFormat;

static time_t parsedate_ansic (const unsigned char *date, const unsigned char *end);

time_t parsedate (const char *date, size_t len)
{
	/*
	 * RFC 2616
	 * http://www.faqs.org/rfcs/rfc2616.html
	 * 3.3.1 Full Date
	 * HTTP applications have historically allowed three different formats
	 * for the representation of date/time stamps:
	 *
	 * Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
	 * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
	 * Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
	 */

	struct tm stm;
	const unsigned char *ptr;
	const unsigned char *end;
	eDateFormat date_format;
	size_t n;
	size_t mday;
	size_t mon;
	size_t year;
	size_t hour;
	size_t min;
	size_t sec;

	if (len < sizeof ("Sun Nov 6 08:49:37 1994") - 1) {
		return -1;
	}

	ptr = (const unsigned char *) date;
	end = ptr + len;

	/* Skip day of the week. */
	while ((ptr < end) && (IS_ALPHA (*ptr))) {
		ptr++;
	}

	n = ptr - (const unsigned char *) date;

	/* ANSI-C? */
	if (IS_WHITE_SPACE (*ptr)) {
		if (n != 3) {
			return -1;
		}

		return parsedate_ansic (ptr + 1, end);
	}

	if ((*ptr != ',') || (n < 3)) {
		return -1;
	}

	/* RFC 850? */
	if (n > 3) {
		date_format = RFC850_DATE_FORMAT;
	} else {
		date_format = RFC822_DATE_FORMAT;
	}

	ptr++;

	/* Skip white spaces. */
	while ((ptr < end) && (IS_WHITE_SPACE (*ptr))) {
		ptr++;
	}

	if ((end - ptr) < sizeof ("06-Nov-94 08:49:37 GMT") - 1) {
		return -1;
	}

	/* Parse day of the month. */
	if ((!IS_DIGIT (*ptr)) || (!IS_DIGIT (*(ptr + 1)))) {
		return -1;
	}

	mday = ((*ptr - '0') * 10) + (*(ptr + 1) - '0');
	if ((mday < 1) || (mday > 31)) {
		return -1;
	}

	ptr += 2;
	if (date_format == RFC822_DATE_FORMAT) {
		if (*ptr != ' ') {
			return -1;
		}
	} else if (*ptr != '-') {
		return -1;
	}

	ptr++;

	/* Parse month. */
	switch (*ptr) {
		case 'J':
			if (*(ptr + 1) == 'a') {
				mon = 0;
			} else {
				if (*(ptr + 2) == 'n') {
					mon = 5;
				} else {
					mon = 6;
				}
			}

			break;
		case 'F':
			mon = 1;
			break;
		case 'M':
			if (*(ptr + 2) == 'r') {
				mon = 2;
			} else {
				mon = 4;
			}

			break;
		case 'A':
			if (*(ptr + 1) == 'p') {
				mon = 3;
			} else {
				mon = 7;
			}

			break;
		case 'S':
			mon = 8;
			break;
		case 'O':
			mon = 9;
			break;
		case 'N':
			mon = 10;
			break;
		case 'D':
			mon = 11;
			break;
		default:
			return -1;
	}

	ptr += 3;

	/* Parse year. */
	if (date_format == RFC822_DATE_FORMAT) {
		if (*ptr != ' ') {
			return -1;
		}

		ptr++;
		if ((!IS_DIGIT (*ptr)) || (!IS_DIGIT (*(ptr + 1))) || (!IS_DIGIT (*(ptr + 2))) || (!IS_DIGIT (*(ptr + 3)))) {
			return -1;
		}

		year = ((*ptr - '0') * 1000) + ((*(ptr + 1) - '0') * 100) + ((*(ptr + 2) - '0') * 10) + (*(ptr + 3) - '0');
		if (year < 1970) {
			return -1;
		}

		ptr += 4;
	} else {
		if (*ptr != '-') {
			return -1;
		}

		ptr++;
		if ((!IS_DIGIT (*ptr)) || (!IS_DIGIT (*(ptr + 1)))) {
			return -1;
		}

		year = ((*ptr - '0') * 10) + (*(ptr + 1) - '0');
		if (year < 70) {
			year += 2000;
		} else {
			year += 1900;
		}

		ptr += 2;
	}

	if (*ptr != ' ') {
		return -1;
	}

	ptr++;
	if ((end - ptr) != sizeof ("08:49:37 GMT") - 1) {
		return -1;
	}

	/* Parse hour. */
	if ((!IS_DIGIT (*ptr)) || (!IS_DIGIT (*(ptr + 1))) || (*(ptr + 2) != ':') || \
			(!IS_DIGIT (*(ptr + 3))) || (!IS_DIGIT (*(ptr + 4))) || (*(ptr + 5) != ':') || \
			(!IS_DIGIT (*(ptr + 6))) || (!IS_DIGIT (*(ptr + 7))) || (*(ptr + 8) != ' ')) {
		return -1;
	}

	hour = ((*ptr - '0') * 10) + (*(ptr + 1) - '0');
	if (hour > 23) {
		return -1;
	}

	min = ((*(ptr + 3) - '0') * 10) + (*(ptr + 4) - '0');
	if (min > 59) {
		return -1;
	}

	sec = ((*(ptr + 6) - '0') * 10) + (*(ptr + 7) - '0');
	if (sec > 59) {
		return -1;
	}

	stm.tm_year = year - 1900;
	stm.tm_mon = mon;
	stm.tm_mday = mday;
	stm.tm_hour = hour;
	stm.tm_min = min;
	stm.tm_sec = sec;
	stm.tm_wday = 0;
	stm.tm_yday = 0;
	stm.tm_isdst = 0;

#ifdef HAVE_TIMEGM
	return timegm (&stm);
#else
	return mktime (&stm) - timezone;
#endif
}

time_t parsedate_ansic (const unsigned char *date, const unsigned char *end)
{
	struct tm stm;
	const unsigned char *ptr;
	size_t mday;
	size_t mon;
	size_t year;
	size_t hour;
	size_t min;
	size_t sec;

	ptr = date;

	/* Skip white spaces. */
	while ((ptr < end) && (IS_WHITE_SPACE (*ptr))) {
		ptr++;
	}

	if ((end - ptr) < sizeof ("Nov 6 08:49:37 1994") - 1) {
		return -1;
	}

	/* Parse month. */
	switch (*ptr) {
		case 'J':
			if (*(ptr + 1) == 'a') {
				mon = 0;
			} else {
				if (*(ptr + 2) == 'n') {
					mon = 5;
				} else {
					mon = 6;
				}
			}

			break;
		case 'F':
			mon = 1;
			break;
		case 'M':
			if (*(ptr + 2) == 'r') {
				mon = 2;
			} else {
				mon = 4;
			}

			break;
		case 'A':
			if (*(ptr + 1) == 'p') {
				mon = 3;
			} else {
				mon = 7;
			}

			break;
		case 'S':
			mon = 8;
			break;
		case 'O':
			mon = 9;
			break;
		case 'N':
			mon = 10;
			break;
		case 'D':
			mon = 11;
			break;
		default:
			return -1;
	}

	ptr += 3;

	if (*ptr != ' ') {
		return -1;
	}

	ptr++;

	/* Parse day of the month. */
	if (!IS_DIGIT (*ptr)) {
		return -1;
	}

	if (IS_DIGIT (*(ptr + 1))) {
		mday = ((*ptr - '0') * 10) + (*(ptr + 1) - '0');
		if ((mday < 1) || (mday > 31)) {
			return -1;
		}

		ptr += 2;
	} else {
		mday = *ptr - '0';
		if (mday < 1) {
			return -1;
		}

		ptr++;
	}

	if (*ptr != ' ') {
		return -1;
	}

	ptr++;

	if ((end - ptr) != sizeof ("08:49:37 1994") - 1) {
		return -1;
	}

	/* Parse hour. */
	if ((!IS_DIGIT (*ptr)) || (!IS_DIGIT (*(ptr + 1))) || (*(ptr + 2) != ':') || \
			(!IS_DIGIT (*(ptr + 3))) || (!IS_DIGIT (*(ptr + 4))) || (*(ptr + 5) != ':') || \
			(!IS_DIGIT (*(ptr + 6))) || (!IS_DIGIT (*(ptr + 7))) || (*(ptr + 8) != ' ')) {
		return -1;
	}

	hour = ((*ptr - '0') * 10) + (*(ptr + 1) - '0');
	if (hour > 23) {
		return -1;
	}

	min = ((*(ptr + 3) - '0') * 10) + (*(ptr + 4) - '0');
	if (min > 59) {
		return -1;
	}

	sec = ((*(ptr + 6) - '0') * 10) + (*(ptr + 7) - '0');
	if (sec > 59) {
		return -1;
	}

	ptr += 9;

	/* Parse year. */
	if ((!IS_DIGIT (*ptr)) || (!IS_DIGIT (*(ptr + 1))) || (!IS_DIGIT (*(ptr + 2))) || (!IS_DIGIT (*(ptr + 3)))) {
		return -1;
	}

	year = ((*ptr - '0') * 1000) + ((*(ptr + 1) - '0') * 100) + ((*(ptr + 2) - '0') * 10) + (*(ptr + 3) - '0');
	if (year < 1970) {
		return -1;
	}

	stm.tm_year = year - 1900;
	stm.tm_mon = mon;
	stm.tm_mday = mday;
	stm.tm_hour = hour;
	stm.tm_min = min;
	stm.tm_sec = sec;
	stm.tm_wday = 0;
	stm.tm_yday = 0;
	stm.tm_isdst = 0;

#ifdef HAVE_TIMEGM
	return timegm (&stm);
#else
	return mktime (&stm) - timezone;
#endif
}
